﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Windows.Threading;

using iStudio.Structure.Extension;
using iStudio.Structure.Message;
using iStudio.Structure.Project;
using iStudio.Structure.Wave;

namespace iStudio.Structure.Engine
{
    public abstract class AudioEngine : ISpectrumPlayer, IDisposable, IObserver<SettingMessage>
    {
        #region Fields

        protected WaveProject _project;

        protected static AudioEngine _instance;

        private readonly DispatcherTimer _positionTimer = new DispatcherTimer(DispatcherPriority.ApplicationIdle);
        //protected readonly BackgroundWorker WaveformGenerateWorker = new BackgroundWorker();
        protected readonly int FftDataSize = (int)Extension.FftDataSize.Fft2048;

        protected bool disposed;

        protected bool canPlay;
        protected bool canPause;
        protected bool canStop;

        protected bool repeat;
        protected bool isPlaying;
        protected bool isRecording;

        protected bool inRepeatSet;

        protected bool inChannelTimerUpdate;
        protected bool inChannelSet;

        protected double channelLength;
        protected double channelPosition;
        protected int channelCount;

        protected string pendingWaveformPath;

        protected TimeSpan repeatStart;
        protected TimeSpan repeatStop;

        #endregion

        #region Constructor

        protected AudioEngine()
        {
            _positionTimer.Interval = TimeSpan.FromMilliseconds(20);
            _positionTimer.Tick += PositionTimerTick;

            //WaveformGenerateWorker.DoWork += WaveformGenerateWorkerDoWork;
            //WaveformGenerateWorker.RunWorkerCompleted += WaveformGenerateWorkerRunWorkerCompleted;
            //WaveformGenerateWorker.WorkerSupportsCancellation = true;

            Project = new WaveProject();
            Project.TrackList.CollectionChanged += TrackListOnCollectionChanged;
        }

        //
        // Only a temp solution
        //

        protected virtual void TrackListOnCollectionChanged (object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            NotifyPropertyChanged ("Project");

            CanPlay = Project.TrackList.Count > 0;
        }

        #endregion

        #region Constants

        protected const int WaveformCompressedPointCount = 2000;
        protected const int RepeatThreshold = 200;

        #endregion

        #region Singleton Pattern

        public static void SetupEngine (AudioEngine engine)
        {
            if (_instance == null)
            {
                _instance = engine;
                _instance.Initialize ();
            }
            else
            {
                throw new NotSupportedException("Unsupported operation: Cannot setup engine second time.");
            }
        }
        
        public static AudioEngine Instance
        {
            get
            {
                if (_instance == null)
                {
                    throw new NotSupportedException("Unsupported operation: First use SetupEngine.");
                }

                return _instance;
            }
        }

        #endregion

        #region IProjectPlayer

        public WaveProject Project
        {
            get
            {
                return _project;
            }
            set
            {
                if (value != null && _project != value)
                {
                    _project = value;
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion

        #region ISpectrumPlayer

        public abstract bool GetFftData(float[] fftDataBuffer);

        public abstract int GetFftFrequencyIndex(int frequency);

        #endregion

        #region IWaveformPlayer
        public virtual TimeSpan SelectionBegin
        {
            get
            {
                return repeatStart;
            }
            set
            {

                if (!inRepeatSet)
                {
                    inRepeatSet = true;

                    TimeSpan oldValue = repeatStart;
                    repeatStart = value;

                    if (oldValue != repeatStart)
                    {
                        NotifyPropertyChanged("SelectionBegin");
                    }

                    inRepeatSet = false;
                }
            }
        }

        public virtual TimeSpan SelectionEnd
        {
            get
            {
                return repeatStop;
            }
            set
            {
                if (!inChannelSet)
                {
                    inRepeatSet = true;

                    TimeSpan oldValue = repeatStop;
                    repeatStop = value;

                    if (oldValue != repeatStop)
                    {
                        NotifyPropertyChanged("SelectionEnd");
                    }

                    inRepeatSet = false;
                }
            }
        }

        [Obsolete("This property is obsolete, use Project.Length instead.")]
        public double ChannelLength
        {
            get
            {
                return channelLength;
            }
            protected set
            {
                double oldValue = channelLength;

                channelLength = value;

                if (Math.Abs (oldValue - channelLength) > Double.Epsilon)
                {
                    NotifyPropertyChanged("ChannelLength");
                }
            }
        }

        public virtual double ChannelPosition
        {
            get
            {
                return channelPosition;
            }
            set
            {
                if (!inChannelSet)
                {
                    inChannelSet = true; // Avoid recursion

                    double oldValue = channelPosition;

                    double position = Math.Max(0, Math.Min(value, Project.Length));

                    channelPosition = position;

                    if (Math.Abs (oldValue - channelPosition) > Double.Epsilon)
                    {
                        NotifyPropertyChanged("ChannelPosition");
                    }

                    inChannelSet = false;
                }
            }
        }

        #endregion

        #region IRecordPlayer

        public bool IsRecording
        {
            get
            {
                return isRecording;
            }
            protected set
            {
                bool oldValue = isRecording;

                isRecording = value;

                if (oldValue != isRecording)
                {
                    NotifyPropertyChanged("IsRecording");
                }

                _positionTimer.IsEnabled = value;
            }
        }

        public bool Record()
        {
            if (IsPlaying)
            {
                Stop();
            }

            CanPlay = false;
            CanPause = false;
            CanStop = true;

            IsRecording = true;

            ExecuteRecord();

            return true;
        }

        #endregion

        #region ISoundPlayer

        public WaveOutputType EngineOutput
        {
            get;
            protected set;
        }

        public float Volume
        {
            get;
            set;
        }

        public bool Repeat
        {
            get
            {
                return repeat;
            }
            set
            {
                bool oldValue = repeat;
                repeat = value;

                if (oldValue != repeat)
                {
                    NotifyPropertyChanged("Repeat");
                }
            }
        }

        public bool CanPlay
        {
            get
            {
                return canPlay;
            }
            protected set
            {
                bool oldValue = canPlay;

                canPlay = value;

                if (oldValue != canPlay)
                {
                    NotifyPropertyChanged("CanPlay");
                }
            }
        }

        public bool CanPause
        {
            get
            {
                return canPause;
            }
            protected set
            {
                bool oldValue = canPause;

                canPause = value;

                if (oldValue != canPause)
                {

                    NotifyPropertyChanged("CanPause");
                }
            }

        }

        public bool CanStop
        {
            get
            {
                return canStop;
            }
            protected set
            {
                bool oldValue = canStop;

                canStop = value;

                if (oldValue != canStop)
                {
                    NotifyPropertyChanged("CanStop");
                }
            }
        }

        public bool IsPlaying
        {
            get
            {
                return isPlaying;
            }
            protected set
            {
                bool oldValue = isPlaying;

                isPlaying = value;

                if (oldValue != isPlaying)
                {
                    NotifyPropertyChanged("IsPlaying");
                }

                _positionTimer.IsEnabled = value;
            }
        }

        public void Stop()
        {
            if (IsPlaying)
            {
                ExecuteStop();

                IsPlaying = false;
            }
            else if (IsRecording)
            {
                ExecuteStopRecord();

                IsRecording = false;
            }

            CanStop = false;
            CanPlay = true;
            CanPause = false;
        }

        public void Pause()
        {
            if (IsPlaying && CanPause)
            {
                ExecutePause();

                IsPlaying = false;

                CanPlay = true;
                CanPause = false;
            }
        }

        public void Play()
        {
            if (CanPlay)
            {
                ExecutePlay ();

                IsPlaying = true;

                CanPause = true;
                CanPlay = false;
                CanStop = true;
            }
        }

        #endregion

        #region IObserver

        public void OnNext(SettingMessage value)
        {
            throw new NotImplementedException();
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnCompleted()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    CloseStream();
                }

                disposed = true;
            }
        }

        #endregion

        /*
        #region Waveform Generation

        protected class WaveformGenerationParams
        {
            public WaveformGenerationParams(int points)
            {
                Points = points;
            }

            public int Points
            {
                get;
                protected set;
            }
        }

        protected void GenerateWaveformData()
        {
            if (WaveformGenerateWorker.IsBusy)
            {
                WaveformGenerateWorker.CancelAsync();

                return;
            }

            if (!WaveformGenerateWorker.IsBusy)
            {
                WaveformGenerateWorker.RunWorkerAsync(new WaveformGenerationParams(WaveformCompressedPointCount));
            }
        }

        protected void WaveformGenerateWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                if (!WaveformGenerateWorker.IsBusy)
                {
                    WaveformGenerateWorker.RunWorkerAsync(new WaveformGenerationParams(WaveformCompressedPointCount));
                }
            }
        }

        protected abstract void WaveformGenerateWorkerDoWork(object sender, DoWorkEventArgs e);

        #endregion       
        */
        #region Protected Utility Methods

        protected abstract void CloseStream();

        #endregion

        #region Engine Abstract Methods

        /// <summary>
        /// Function initializes AudioEngine with default input and output devices
        /// </summary>
        public void Initialize ()
        {
            Initialize (-1, -1);
        }

        /// <summary>
        /// Function initializes AudioEngine with default input device
        /// </summary>
        /// <param name="outDeviceNumber">Output device number</param>
        public void Initialize (int outDeviceNumber)
        {
            Initialize (outDeviceNumber, -1);
        }

        public abstract void Initialize (int outDeviceNumber, int inDeviceNumber);

        protected abstract void ExecutePlay ();
        protected abstract void ExecutePause ();
        protected abstract void ExecuteStop ();

        protected abstract void ExecuteRecord ();

        protected abstract void ExecuteStopRecord ();

        //
        // Output sample providers
        //

        public abstract OutputSampleProvider GetChannelOutputProviders(WaveChannel channel);

        public abstract OutputSampleProvider GetEngineOutputProviders(); 
        #endregion

        #region Event Handlers
        protected abstract void PositionTimerTick(object sender, EventArgs e);
        #endregion
    }
}
